require_relative './lex'
require_relative './ast'

module Stone
  class StoneParser < Y::Parser

    ops = Operators
      .add("=",  1, Operators::RIGHT)
      .add("==", 2, Operators::LEFT)
      .add(">",  2, Operators::LEFT)
      .add("<",  2, Operators::LEFT)
      .add("+",  3, Operators::LEFT)
      .add("-",  3, Operators::LEFT)
      .add("*",  4, Operators::LEFT)
      .add("/",  4, Operators::LEFT)
      .add("%",  4, Operators::LEFT)

    reversed = [';', '}', ')', Lex::EOL]

    expr0 = rule
    # todo bug : there's to many [] (fixed)
    postfix0 = rule

    primary = rule(PrimaryExpr)
      .or(rule.sep('(').ast(expr0).sep(')'),
          rule.number(NumberLiteray),
          rule.identifier(reversed, Name))
      .repeat(postfix0)

    factor = rule.or(rule(NegExpr).sep('-').ast(primary),
                     primary)

    expr = expr0.expression(BinaryExpr, factor, ops)

    statement0 = rule
    block = rule(BlockStmnt)
      .sep('{')
      .option(statement0)
      .repeat(rule.sep(';', Lex::EOL).option(statement0))
      .sep('}')

    simple = rule(PrimaryExpr).ast(expr)

    statement = statement0.or(
      rule(IfStmnt)
        .sep('if').ast(expr)
        .ast(block)
        # can't insert 'NullStmnt' into rule(),
        # because IfStmnt#else_block will fetch 
        # the third elem of children, so maybe 
        # has the empty lines which will occupat 
        # the 3rd position
        .repeat(rule().sep(Lex::EOL))
        .option(rule.sep('else').ast(block)),
      rule(WhileStmnt)
        .sep('while').ast(expr)
        .ast(block),
      simple)

    # when defining function
    param = rule.identifier(reversed) 
    params = rule(ParameterList)
      .ast(param).repeat(rule.sep(',').ast(param))
    param_list = rule.sep('(').maybe(params).sep(')')

    fn = rule(FnStmnt)
      .sep('fn').identifier(reversed)
      .ast(param_list)
      .ast(block)

    # when calling function
    args = rule(Arguments)
      .ast(expr).repeat(rule.sep(',').ast(expr))
    postfix = postfix0
      .sep('(').maybe(args).sep(')')

    PROGRAM = rule.or(fn, 
                      statement, 
                      # work out the program that can't 
                      # have empty line by adding the 
                      # function 'evaluate' to 'NullStmnt'
                      rule(NullStmnt))
                  .sep(';', Lex::EOL)

    def initialize
    end

    def parse(lex)
      PROGRAM.parse(lex)
    end
  end
end
